#include<mutex>
#include<iostream>
using namespace std;

namespace Mango
{

template<class T>
class auto_ptr
{
public:
  auto_ptr(T* ptr = nullptr) :_ptr(ptr)
  {}
  ~auto_ptr()
  {
    if(_ptr)
    {
      delete _ptr;
      _ptr = nullptr;
    }
  }

  auto_ptr(auto_ptr<T>& ap)  :_ptr(ap._ptr)
  {
    ap._ptr = nullptr;
  }
  auto_ptr<T>& operator=(const auto_ptr<T>& ap)
  {
    if(this != &ap)
    {
      _ptr = ap._ptr;
      ap._ptr = nullptr;
    }
    return *this;
  }
  T* operator->()
  {
    return _ptr;
  }
  T& operator*()
  {
    return *_ptr;
  }
private:
  T* _ptr;
};

template<typename T>
class unique_ptr
{
public:
  T* operator->()
  {
    return _ptr;
  }
  T& operator*()
  {
    return *_ptr;
  }
private:
  unique_ptr(T* ptr = nullptr) :_ptr(ptr)
  {}
  unique_ptr(const unique_ptr<T>&) = delete;
  unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;
private:
  T* _ptr;
};


template<class T>
class shared_ptr
{
public:
  shared_ptr(T* ptr = nullptr)
    :_ptr(ptr),_mtx(new mutex()),_pRefCount(new int(1))
  {}
  void AddRef()
  {
     _mtx->lock();
     (*_pRefCount)++;
     _mtx->unlock();
  }
  void ReleaseRef()
  {
    _mtx->lock();
    bool flag = false;
    if(--(*_pRefCount) == 0)
    {
      flag = true;
      if(_ptr != nullptr)
      {
        delete _ptr;
        _ptr = nullptr;
      }
      delete _pRefCount;
    }
    _mtx->unlock();
    if(flag)
    {
      delete _mtx;
    }
  }
  ~shared_ptr()
  {
    ReleaseRef();
  }
  shared_ptr(const shared_ptr<T>& sp)
    :_ptr(sp._ptr),_mtx(sp._mtx),_pRefCount(sp._pRefCount)
  {
   AddRef(); 
  }
  shared_ptr<T>& operator=(const shared_ptr<T>& sp)
  {
    if(_ptr != sp._ptr)
    {
      ReleaseRef();
      _ptr = sp._ptr;
      _mtx = sp._mtx;
      _pRefCount = sp._pRefCount;
      AddRef();
    }
    return *this;
  }
  T* operator->()
  {
    return _ptr;
  }
  T& operator*()
  {
    return *_ptr;
  }
  int use_count() const 
  {
    return *_pRefCount;
  }
  T* get()
  {
    return _ptr;
  }
private:
  T* _ptr;
  mutex* _mtx;
  int* _pRefCount;
  
};

template<class T>
class weak_ptr
{
public:
  weak_ptr() :_ptr(nullptr)
  {}
  weak_ptr(const shared_ptr<T>& sp)
    :_ptr(sp.get())
  {}
  weak_ptr<T>& operator=(const shared_ptr<T>& sp)
  {
    _ptr = sp.get();
    return *this;
  }
  ~weak_ptr()
  {
    if(_ptr)
    {
      delete _ptr;
      _ptr = nullptr;
    }
  }
  T* operator->()
  {
    return _ptr;
  }
  T& operator*()
  {
    return *_ptr;
  }
  T* get() const 
  {
    return _ptr;
  }
private:
    T* _ptr;
};

}
